
#include "stb/stb_image.h"
#include "vulkan/VulkanLib.h"

USING_NS_VULKAN

const int Width = 800;
const int Height = 600;

class AppImage : public AppBase
{
public:
    AppImage() : AppBase(Width, Height, "Vulkan Render") {}

    void init() override
    {
        std::vector<Model2DTex::Vertex> vertices{
            {{-1.0f,  1.0f}, {0.0f, 1.0f}, {1.0, 1.0, 1.0}},
            {{-1.0f, -1.0f}, {0.0f, 0.0f}, {1.0, 1.0, 1.0}},
            {{ 1.0f,  1.0f}, {1.0f, 1.0f}, {1.0, 1.0, 1.0}},

            {{ 1.0f,  1.0f}, {1.0f, 1.0f}, {1.0, 1.0, 1.0}},
            {{-1.0f, -1.0f}, {0.0f, 0.0f}, {1.0, 1.0, 1.0}},
            {{ 1.0f, -1.0f}, {1.0f, 0.0f}, {1.0, 1.0, 1.0}},
        };
        model = Model2DTex::create(this->device, vertices);
        const int texWidth = 720;
        const int texHeight = 720;
        auto data = readFile("asset/1-i420.yuv");

        this->textureY = Texture::create(this->device, texWidth, texHeight, VK_FORMAT_R8_UNORM);
        this->textureY->update(reinterpret_cast<uint8_t*>(data.data()), texWidth*texHeight);
        this->textureU = Texture::create(this->device, texWidth/2, texHeight/2, VK_FORMAT_R8_UNORM);
        this->textureU->update(reinterpret_cast<uint8_t*>(data.data()) + texWidth*texHeight, texWidth*texHeight/4);
        this->textureV = Texture::create(this->device, texWidth/2, texHeight/2, VK_FORMAT_R8_UNORM);
        this->textureV->update(reinterpret_cast<uint8_t*>(data.data()) + texWidth*texHeight / 4 * 5, texWidth*texHeight/4);

//        this->createTexture("asset/Lenna.png");
        this->createTexture1("asset/Lenna.png");
        createTextureSampler();

        auto shader = Shader::createByPath("asset/yuv.vert", "asset/yuv.frag");
        descriptorSetLayoutAttrib = shader->getDescriptorSetLayout();
        descriptorSetLayout = descriptorSetLayoutAttrib.createVkLayout(device->getDevice());
        this->createUniform();
        this->createDescriptorPool();
        this->createDescriptorSets();

        PipelineConfigInfo pipelineConfig{};
        Pipeline::defaultPipelineConfigInfo(pipelineConfig);
        pipelineConfig.setLayouts.push_back(descriptorSetLayout);
        pipelineConfig.vertexBindings = Model2DTex::Vertex::getBindingDescriptions();
        pipelineConfig.vertexAttributes = Model2DTex::Vertex::getAttributeDescriptions();
        pipelineConfig.renderPass = swapChain->getRenderPass();

        pipeline = Pipeline::create(this->device, shader, pipelineConfig);

        createCommandBuffers();
    }

    void drawFrame() override
    {
        uint32_t imageIndex{};
        auto result = swapChain->acquireNextImage(imageIndex);

        if (result == VK_ERROR_OUT_OF_DATE_KHR)
        {
            recreateSwapChain();
            return;
        }
        else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
        {
            throw std::runtime_error("failed to acquire swap chain image");
        }
        updateUniformBuffer(imageIndex);
        recordCommandBuffer(imageIndex);
        result = swapChain->submitCommandBuffers(&commandBuffers[imageIndex], imageIndex);

        if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
        {
            recreateSwapChain();
        }
        else if (result != VK_SUCCESS)
        {
            throw std::runtime_error("failed to present swap chain image!");
        }
    }

    void updateUniformBuffer(int i)
    {
        auto& buffer = this->uniformBuffers[i];
        const auto& size = this->window->getSize();

        math::Mat4 proj(1.0f);
        math::Mat4 view(1.0f);
        math::Mat4 rotation(1.0f);
        math::Mat4 scale(1.0f);

        proj = math::orgho(-size.x * 0.5f, size.x * 0.5f, -size.y * 0.5f, size.y * 0.5f, -100, 100);
        scale = math::scale({imageWidth, imageHeight, 1.0f});

        auto time = (float)TimeSys::getTime();
        constexpr float period = 5.0f;
//        rotation = math::rotate(math::pi_2 * std::fmodf(time, period) / period, math::Z_Axis);

        buffer->setField("proj", proj);
        buffer->setField("view", view);
        buffer->setField("model", rotation * scale);

        buffer->flush();
    }

    void cleanup() override
    {
        vkFreeCommandBuffers(device->getDevice(), device->getCommandPool(),
                             static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
        commandBuffers.clear();

        this->textureY.reset();
        this->textureU.reset();
        this->textureV.reset();
        model.reset();
        pipeline.reset();
        this->uniformBuffers.clear();
        
        descriptorSets.clear();

        if (this->textureSampler != nullptr)
        {
            vkDestroySampler(device->getDevice(), textureSampler, nullptr);
            textureSampler = nullptr;
        }

        if (this->descriptorSetLayout != nullptr)
        {
            vkDestroyDescriptorSetLayout(device->getDevice(), descriptorSetLayout, nullptr);
            descriptorSetLayout = nullptr;
        }

        if (this->descriptorPool != nullptr)
        {
            vkDestroyDescriptorPool(device->getDevice(), descriptorPool, nullptr);
            this->descriptorPool = nullptr;
        }

        if (this->imageViewYuv != nullptr)
        {
            vkDestroyImageView(device->getDevice(), imageViewYuv, nullptr);
        }

        if (this->imageYuv != nullptr)
        {
            vkDestroyImage(device->getDevice(), imageYuv, nullptr);
            this->imageYuv = nullptr;
        }
        for (auto& memory : imageMemoryYuv)
        {
            if (memory != nullptr)
            {
                vkFreeMemory(device->getDevice(), memory, nullptr);
                memory = nullptr;
            }
        }
    }

    void createCommandBuffers()
    {
        commandBuffers.resize(swapChain->imageCount());

        VkCommandBufferAllocateInfo alloInfo{};
        alloInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
        alloInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
        alloInfo.commandPool = device->getCommandPool();
        alloInfo.commandBufferCount = static_cast<uint32_t>(commandBuffers.size());
        if (vkAllocateCommandBuffers(device->getDevice(), &alloInfo, commandBuffers.data()) != VK_SUCCESS)
        {
            throw std::runtime_error("failed to allocate command buffers.");
        }
    }

    void recordCommandBuffer(uint32_t imageIndex)
    {
        VkCommandBufferBeginInfo beginInfo{};
        beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

        if (vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo) != VK_SUCCESS)
        {
            throw std::runtime_error("failed to begin recording command buffer!");
        }

        VkRenderPassBeginInfo renderPassInfo{};
        renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
        renderPassInfo.renderPass = swapChain->getRenderPass();
        renderPassInfo.framebuffer = swapChain->getFrameBuffer(imageIndex);
        renderPassInfo.renderArea.offset = { 0, 0 };
        renderPassInfo.renderArea.extent = swapChain->getExtent();

        std::array<VkClearValue, 2> clearValues{};
        clearValues[0].color = { 0.01f, 0.01f, 0.01f, 1.0f };
        clearValues[1].depthStencil = { 1.0f, 0 };
        renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
        renderPassInfo.pClearValues = clearValues.data();

        vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

        VkViewport viewport{};
        viewport.x = 0.0f;
        viewport.y = 0.0f;
        viewport.width = static_cast<float>(swapChain->getWidth());
        viewport.height = static_cast<float>(swapChain->getHeight());
        viewport.minDepth = 0.0f;
        viewport.maxDepth = 1.0f;
        VkRect2D scissor{ {0, 0}, swapChain->getExtent() };
        vkCmdSetViewport(commandBuffers[imageIndex], 0, 1, &viewport);
        vkCmdSetScissor(commandBuffers[imageIndex], 0, 1, &scissor);

        vkCmdBindPipeline(commandBuffers[imageIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->getPipeline());
        vkCmdBindDescriptorSets(commandBuffers[imageIndex], VK_PIPELINE_BIND_POINT_GRAPHICS,
                                pipeline->getPipelineLayout(), 0, 1,
                                &descriptorSets[imageIndex]->getDescriptorSet(), 0, nullptr);

        model->bind(commandBuffers[imageIndex]);
        model->draw(commandBuffers[imageIndex]);

        vkCmdEndRenderPass(commandBuffers[imageIndex]);
        if (vkEndCommandBuffer(commandBuffers[imageIndex]) != VK_SUCCESS)
        {
            throw std::runtime_error("faild to record command buffer");
        }
    }

    void recreateSwapChain()
    {
        vkDeviceWaitIdle(device->getDevice());

        swapChain->recreate();
        if (swapChain->imageCount() != commandBuffers.size())
        {
            vkFreeCommandBuffers(device->getDevice(), device->getCommandPool(),
                                 static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
            commandBuffers.clear();

            createCommandBuffers();
        }
    }

    void createUniform()
    {
        this->uniformBuffers.resize(swapChain->imageCount());
        for (int i = 0; i < uniformBuffers.size(); ++i)
        {
            this->uniformBuffers[i] = UniformBuffer::create(device, descriptorSetLayoutAttrib.blocks[0]);
        }
    }

    void createDescriptorPool() {
        std::array<VkDescriptorPoolSize, 2> poolSizes{};
        poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
        poolSizes[0].descriptorCount = static_cast<uint32_t>(swapChain->imageCount()*4);
        poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
        poolSizes[1].descriptorCount = static_cast<uint32_t>(swapChain->imageCount() * 16);

        VkDescriptorPoolCreateInfo poolInfo{};
        poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
        poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
        poolInfo.pPoolSizes = poolSizes.data();
        poolInfo.maxSets = static_cast<uint32_t>(swapChain->imageCount());

        if (vkCreateDescriptorPool(device->getDevice(), &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
            throw std::runtime_error("failed to create descriptor pool!");
        }
    }

    void createDescriptorSets()
    {
        for (size_t i = 0; i < swapChain->imageCount(); i++) {

            auto descriptor = DescriptorSet::create(device, descriptorSetLayout, descriptorPool);

            descriptor->addBuffer(uniformBuffers[i], descriptorSetLayoutAttrib.blocks[0].binding);
            descriptor->addSampler(this->textureY, textureSampler, descriptorSetLayoutAttrib.samples["texSamplerY"].binging);
            descriptor->addSampler(this->textureU, textureSampler, descriptorSetLayoutAttrib.samples["texSamplerU"].binging);
            descriptor->addSampler(this->textureV, textureSampler, descriptorSetLayoutAttrib.samples["texSamplerV"].binging);
            descriptor->build();

            this->descriptorSets.push_back(descriptor);
        }
    }

    void createTextureSampler()
    {
        VkSamplerCreateInfo samplerInfo{};
        samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
        samplerInfo.magFilter = VK_FILTER_LINEAR;
        samplerInfo.minFilter = VK_FILTER_LINEAR;
        samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
        samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
        samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
        samplerInfo.anisotropyEnable = VK_FALSE;
        samplerInfo.maxAnisotropy = 0;
        samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
        samplerInfo.unnormalizedCoordinates = VK_FALSE;
        samplerInfo.compareEnable = VK_FALSE;
        samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
        samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
        samplerInfo.minLod = 0.0f;
        samplerInfo.maxLod = 0.0f;
        samplerInfo.mipLodBias = 0.0f;

        if (vkCreateSampler(device->getDevice(), &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) {
            throw std::runtime_error("failed to create texture sampler!");
        }
    }

    void createTexture1(const std::string& path)
    {
        int channels_in_file;
        uint8_t* imageData = stbi_load(path.c_str(), &imageWidth, &imageHeight, &channels_in_file, STBI_rgb_alpha);
        int dataSize = imageWidth * imageHeight * STBI_rgb_alpha;
        auto imageFormat = VK_FORMAT_R8G8B8A8_UNORM;

        setTextureStorage1(imageWidth, imageHeight);

        setTextureData1(imageData, dataSize, imageWidth, imageHeight);

        stbi_image_free(imageData);
    }

    void setTextureStorage1(int width, int height)
    {
        VkImageCreateInfo imageInfo{};
        imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
        imageInfo.imageType = VK_IMAGE_TYPE_2D;
        imageInfo.extent.width = width;
        imageInfo.extent.height = height;
        imageInfo.extent.depth = 1;
        imageInfo.mipLevels = 1;
        imageInfo.arrayLayers = 1;
        imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
        imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
        imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
        imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
        imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;

        if (vkCreateImage(device->getDevice(), &imageInfo, nullptr, &imageYuv) != VK_SUCCESS) {
            throw std::runtime_error("failed to create image!");
        }

        VkMemoryRequirements memRequirements;
        vkGetImageMemoryRequirements(device->getDevice(), imageYuv, &memRequirements);

        VkMemoryAllocateInfo allocInfo{};
        allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
        allocInfo.allocationSize = memRequirements.size;
        allocInfo.memoryTypeIndex = device->findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

        if (vkAllocateMemory(device->getDevice(), &allocInfo, nullptr, &imageMemoryYuv[0]) != VK_SUCCESS) {
            throw std::runtime_error("failed to allocate image memory!");
        }

        if (vkBindImageMemory(device->getDevice(), imageYuv, imageMemoryYuv[0], 0) != VK_SUCCESS) {
            throw std::runtime_error("failed to bind image memory!");
        }


        VkImageViewCreateInfo viewInfo {};
        viewInfo.sType                           = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
        viewInfo.image                           = imageYuv;
        viewInfo.viewType                        = VK_IMAGE_VIEW_TYPE_2D;
        viewInfo.format                          = VK_FORMAT_R8G8B8A8_UNORM;
        viewInfo.subresourceRange.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
        viewInfo.subresourceRange.baseMipLevel   = 0;
        viewInfo.subresourceRange.levelCount     = 1;
        viewInfo.subresourceRange.baseArrayLayer = 0;
        viewInfo.subresourceRange.layerCount     = 1;

        if (vkCreateImageView(device->getDevice(), &viewInfo, nullptr, &imageViewYuv) != VK_SUCCESS)
        {
            throw std::runtime_error("failed to create texture image view!");
        }
    }

    void setTextureData1(uint8_t* pixelData, int dataSize, int width, int height)
    {

        VkBuffer stagingBuffer;
        VkDeviceMemory stagingBufferMemory;
        device->createBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
                             VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                             stagingBuffer, stagingBufferMemory);
        uint8_t* data{};
        vkMapMemory(device->getDevice(), stagingBufferMemory, 0, dataSize, 0, (void**)&data);
        memcpy(data, pixelData, dataSize);
        vkUnmapMemory(device->getDevice(), stagingBufferMemory);


        VkCommandBuffer commandBuffer = device->beginSingleTimeCommands();
        transitionImageLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, 1, commandBuffer);

        VkBufferImageCopy region{};
        region.bufferOffset = 0;
        region.bufferRowLength = 0;
        region.bufferImageHeight = 0;

        region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        region.imageSubresource.mipLevel = 0;
        region.imageSubresource.baseArrayLayer = 0;
        region.imageSubresource.layerCount = 1;

        region.imageOffset = {0, 0, 0};
        region.imageExtent = {(uint32_t)width, (uint32_t)height, 1};

        vkCmdCopyBufferToImage(
            commandBuffer,
            stagingBuffer,
            imageYuv,
            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
            1,
            &region);


        transitionImageLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, 1, commandBuffer);

        device->endSingleTimeCommands(commandBuffer);



        vkDestroyBuffer(device->getDevice(), stagingBuffer, nullptr);
        vkFreeMemory(device->getDevice(), stagingBufferMemory, nullptr);
    }

    void createTexture(const std::string& path)
    {
        int channels_in_file;
        uint8_t* imageData = stbi_load(path.c_str(), &imageWidth, &imageHeight, &channels_in_file, STBI_rgb_alpha);
        int dataSize = imageWidth * imageHeight * STBI_rgb_alpha;
        auto imageFormat = VK_FORMAT_R8G8B8A8_UNORM;

        setTextureStorage(imageWidth, imageHeight);

        setTextureData(imageData, dataSize, imageWidth, imageHeight);

        stbi_image_free(imageData);
    }

    void setTextureStorage(int width, int height)
    {
        VkImageCreateInfo imageInfo{};
        imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
        imageInfo.imageType = VK_IMAGE_TYPE_2D;
        imageInfo.extent.width = width;
        imageInfo.extent.height = height;
        imageInfo.extent.depth = 1;
        imageInfo.mipLevels = 1;
        imageInfo.arrayLayers = 1;
        imageInfo.format = VK_FORMAT_G8B8G8R8_422_UNORM;
        imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
        imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
        imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
        imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
        imageInfo.flags = VK_IMAGE_CREATE_DISJOINT_BIT;

        if (vkCreateImage(device->getDevice(), &imageInfo, nullptr, &imageYuv) != VK_SUCCESS) {
            throw std::runtime_error("failed to create image!");
        }

        VkImageAspectFlagBits aspects[3] {VK_IMAGE_ASPECT_PLANE_0_BIT, VK_IMAGE_ASPECT_PLANE_1_BIT, VK_IMAGE_ASPECT_PLANE_2_BIT};
        VkImagePlaneMemoryRequirementsInfo imagePlaneMemoryRequirementsInfo {};
        imagePlaneMemoryRequirementsInfo.sType = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO;

        VkImageMemoryRequirementsInfo2 imageMemoryRequirementsInfo2 = {};
        imageMemoryRequirementsInfo2.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2;
        imageMemoryRequirementsInfo2.pNext = &imagePlaneMemoryRequirementsInfo;
        imageMemoryRequirementsInfo2.image = imageYuv;

        VkMemoryRequirements2 memoryRequirements2{};
        memoryRequirements2.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2;

        VkMemoryAllocateInfo memoryAllocateInfo{};
        memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;

        // Bind plane 0 memory
        VkBindImagePlaneMemoryInfo bindImagePlaneMemoryInfos[3]{};

        VkBindImageMemoryInfo bindImageMemoryInfos[3]{};

        for (int i = 0; i < 3; ++i)
        {
            imagePlaneMemoryRequirementsInfo.planeAspect = aspects[i];

            // Get memory requirement for each plane
            vkGetImageMemoryRequirements2(device->getDevice(), &imageMemoryRequirementsInfo2, &memoryRequirements2);

            // Allocate plane 0 memory
            memoryAllocateInfo.allocationSize  = memoryRequirements2.memoryRequirements.size;
            memoryAllocateInfo.memoryTypeIndex = memoryRequirements2.memoryRequirements.memoryTypeBits;
            vkAllocateMemory(device->getDevice(), &memoryAllocateInfo, nullptr, &imageMemoryYuv[i]);

            bindImagePlaneMemoryInfos[i].sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO;
            bindImagePlaneMemoryInfos[i].planeAspect = aspects[i];

            bindImageMemoryInfos[i].sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO;
            bindImageMemoryInfos[i].pNext = &bindImagePlaneMemoryInfos[i];
            bindImageMemoryInfos[i].image = imageYuv;
            bindImageMemoryInfos[i].memory       = imageMemoryYuv[i];

        }

        // Bind the same for each plane
        vkBindImageMemory2(device->getDevice(), 3, bindImageMemoryInfos);


        VkImageViewCreateInfo viewInfo {};
        viewInfo.sType                           = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
        viewInfo.image                           = imageYuv;
        viewInfo.viewType                        = VK_IMAGE_VIEW_TYPE_2D;
        viewInfo.format                          = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM;
        viewInfo.subresourceRange.aspectMask     = VK_IMAGE_ASPECT_PLANE_0_BIT|VK_IMAGE_ASPECT_PLANE_1_BIT|VK_IMAGE_ASPECT_PLANE_2_BIT;
        viewInfo.subresourceRange.baseMipLevel   = 0;
        viewInfo.subresourceRange.levelCount     = 1;
        viewInfo.subresourceRange.baseArrayLayer = 0;
        viewInfo.subresourceRange.layerCount     = 1;

        if (vkCreateImageView(device->getDevice(), &viewInfo, nullptr, &imageViewYuv) != VK_SUCCESS)
        {
            throw std::runtime_error("failed to create texture image view!");
        }
    }

    void setTextureData(uint8_t* pixelData, int dataSize, int width, int height)
    {

        VkBuffer stagingBuffer;
        VkDeviceMemory stagingBufferMemory;
        device->createBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
                              VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                              stagingBuffer, stagingBufferMemory);
        uint8_t* data{};
        vkMapMemory(device->getDevice(), stagingBufferMemory, 0, dataSize, 0, (void**)&data);
        memcpy(data, pixelData, dataSize);
        vkUnmapMemory(device->getDevice(), stagingBufferMemory);


        VkCommandBuffer commandBuffer = device->beginSingleTimeCommands();
        transitionImageLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, 1, commandBuffer);

        VkBufferImageCopy region{};
        region.bufferOffset = 0;
        region.bufferRowLength = 0;
        region.bufferImageHeight = 0;

        region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        region.imageSubresource.mipLevel = 0;
        region.imageSubresource.baseArrayLayer = 0;
        region.imageSubresource.layerCount = 1;

        region.imageOffset = {0, 0, 0};
        region.imageExtent = {(uint32_t)width, (uint32_t)height, 1};

        vkCmdCopyBufferToImage(
            commandBuffer,
            stagingBuffer,
            imageYuv,
            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
            1,
            &region);


        transitionImageLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, 1, commandBuffer);

        device->endSingleTimeCommands(commandBuffer);



        vkDestroyBuffer(device->getDevice(), stagingBuffer, nullptr);
        vkFreeMemory(device->getDevice(), stagingBufferMemory, nullptr);
    }

    void transitionImageLayout(VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t baseLevel, uint32_t levelCount, VkCommandBuffer commandBuffer)
    {
        VkImageMemoryBarrier barrier{};
        barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
        barrier.oldLayout = oldLayout;
        barrier.newLayout = newLayout;
        barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        barrier.image = imageYuv;
        barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        barrier.subresourceRange.baseMipLevel = baseLevel;
        barrier.subresourceRange.levelCount = levelCount;
        barrier.subresourceRange.baseArrayLayer = 0;
        barrier.subresourceRange.layerCount = 1;

        VkPipelineStageFlags sourceStage;
        VkPipelineStageFlags destinationStage;

        if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
        {
            barrier.srcAccessMask = 0;
            barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;

            sourceStage      = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
            destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
        }
        else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
                 newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
        {
            barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
            barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

            sourceStage      = VK_PIPELINE_STAGE_TRANSFER_BIT;
            destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
        }
        // for getGuidAndDepthOfMouseClickOnRenderSceneForUI() get depthimage
        else if (oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL &&
                 newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
        {
            barrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
            barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;

            sourceStage      = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
            destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
        }
        else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL &&
                 newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
        {
            barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
            barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;

            sourceStage      = VK_PIPELINE_STAGE_TRANSFER_BIT;
            destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
        }
        // for generating mipmapped image
        else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
                 newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
        {
            barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
            barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;

            sourceStage      = VK_PIPELINE_STAGE_TRANSFER_BIT;
            destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
        }
        else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL &&
                 newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
        {
            barrier.srcAccessMask = 0;
            barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

            sourceStage      = VK_PIPELINE_STAGE_TRANSFER_BIT;
            destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
        }
        else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL &&
                 newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
        {
            barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
            barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;

            sourceStage      = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
            destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
        }
        else
        {
            throw std::invalid_argument("unsupported layout transition!");
        }

        //    VkCommandBuffer commandBuffer = context->beginSingleTimeCommands();
        vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0,
                             0, nullptr, 0, nullptr,
                             1, &barrier);
        //    context->endSingleTimeCommands(commandBuffer);

    }


private:

    Model2DTexRef model;
    PipelineRef  pipeline;
    DescriptorSetLayout descriptorSetLayoutAttrib;

    VkDescriptorSetLayout descriptorSetLayout{};
    VkDescriptorPool descriptorPool{};
    std::vector<VkCommandBuffer> commandBuffers;
    std::vector<UniformBufferRef> uniformBuffers;
    std::vector<DescriptorSetRef> descriptorSets;
    VkSampler textureSampler{};

    TextureRef textureY{};
    TextureRef textureU{};
    TextureRef textureV{};

    VkImage imageYuv{};
    VkDeviceMemory imageMemoryYuv[3]{}; // 0-Y, 1-U, 2-V
    VkImageView imageViewYuv{};
    int imageWidth{0};
    int imageHeight{0};
};

AppBase* getApplication()
{
    static AppImage app;
    return &app;
}